package com.demo.elastic;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

import static com.demo.elastic.ExporterProperties.ElasticConfig.DEFAULTS;
import static org.apache.commons.lang.StringUtils.*;

public class ExporterConfiguration {
    private static Logger logger = LoggerFactory.getLogger(ExporterConfiguration.class);
    private static final String COLON = ":";
    private static final String COMMA = ",";
    private final ExporterProperties properties;

    private ExporterConfiguration(ExporterProperties properties) {
        this.properties = properties;
    }

    private static final Object lockHelper = new Object();
    private Client source = null;
    private Client target = null;
    private final static int DEFAULT_SIZE = 2000;
    private int size = DEFAULT_SIZE;

    static ExporterConfiguration parse(String[] args) {
        Map<String, String> params = new HashMap<>(args.length);
        for (String arg : args) {
            if (!StringUtils.startsWith(arg, "--")) {
                continue;
            }
            String[] arr = StringUtils.split(arg, "=");
            if (arr.length != 2) {
                continue;
            }
            if (StringUtils.length(arr[0]) <= 2 || StringUtils.isEmpty(arr[1])) {
                continue;
            }
            params.put(StringUtils.substring(arr[0], 2), arr[1]);
        }
        ExporterProperties properties = ExporterProperties.parse(params);
        ExporterConfiguration configuration = new ExporterConfiguration(properties);
        if (params.containsKey("size")) {
            int num = NumberUtils.toInt(params.get("size"), DEFAULT_SIZE);
            if (num <= 0) {
                throw new IllegalArgumentException("size参数必须大于0");
            }
            configuration.size = num;
        }
        return configuration;
    }

    Client sourceClient() {
        try {
            if (source == null) {
                synchronized (lockHelper) {
                    if (source == null) {
                        source = createClient(properties.getSource());
                    }
                }
            }
            return source;
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    Client targetClient() {
        try {
            if (target == null) {
                synchronized (lockHelper) {
                    if (target == null) {
                        target = createClient(properties.getTarget());
                    }
                }
            }
            return target;
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    private Client createClient(ExporterProperties.ElasticConfig config) throws Exception {
        if (StringUtils.isNotBlank(config.getClusterNodes())) {
            return createTransportClient(config);
        }
        return createNodeClient(config);
    }

    private Client createNodeClient(ExporterProperties.ElasticConfig config) {
        Settings.Builder settings = Settings.settingsBuilder();
        for (Map.Entry<String, String> entry : DEFAULTS.entrySet()) {
            if (!config.getProperties().containsKey(entry.getKey())) {
                settings.put(entry.getKey(), entry.getValue());
            }
        }
        settings.put(config.getProperties());
        Node node = new NodeBuilder().settings(settings).clusterName(config.getClusterName()).node();
        return node.client();
    }

    private Client createTransportClient(ExporterProperties.ElasticConfig config) throws Exception {
        TransportClient client = TransportClient.builder().settings(settings(config)).build();
        String clusterNodes = config.getClusterNodes();
        hasText(clusterNodes, "[Assertion failed] clusterNodes settings missing.");
        for (String clusterNode : split(clusterNodes, COMMA)) {
            String hostName = substringBeforeLast(clusterNode, COLON);
            String port = substringAfterLast(clusterNode, COLON);
            hasText(hostName, "[Assertion failed] missing host name in 'clusterNodes'");
            hasText(port, "[Assertion failed] missing port in 'clusterNodes'");
            client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(hostName), Integer.valueOf(port)));
        }
        client.connectedNodes();
        return client;
    }

    private Settings settings(ExporterProperties.ElasticConfig config) {
        return Settings.builder().put("cluster.name", config.getClusterName()).put("client.transport.sniff", !"false".equals(config.getProperties().getOrDefault("client.transport.sniff", "true"))).put("client.transport.ignore_cluster_name", org.apache.commons.lang.StringUtils.equalsIgnoreCase("true", config.getProperties().get("client.transport.ignore_cluster_name"))).put("client.transport.ping_timeout", config.getProperties().getOrDefault("client.transport.ping_timeout", "5s")).put("client.transport.nodes_sampler_interval", config.getProperties().getOrDefault("client.transport.nodes_sampler_interval", "5s")).build();
    }

    public void destroy() {
        try {
            if (source != null) {
                logger.info("destroy source client.");
                source.close();
            }
        } finally {
            source = null;
        }
        try {
            if (target != null) {
                logger.info("destroy target client.");
                target.close();
            }
        } finally {
            target = null;
        }
    }

    private void hasText(String text, String errorMsg) throws Exception {
        if (StringUtils.isEmpty(text)) {
            throw new IllegalArgumentException(errorMsg);
        }
    }

    public int getSize() {
        return size;
    }
}